package com.yjymm.service;

import com.yjymm.dao.COVIDDataDao;
import com.yjymm.domain.COVIDData;
import com.yjymm.domain.RegionData;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/**
 * @author yjymm
 * @date 2020-05-09 17:12
 */

@Service
public class COVIDDataService {

    private static final Logger logger = LoggerFactory.getLogger(COVIDDataService.class);

    private static final String BASE_URL = "https://gitee.com/yjymm/COVID-19/raw/master" +
            "/csse_covid_19_data/csse_covid_19_daily_reports/%02d-%02d-%d.csv";

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private COVIDDataDao dao;

    //记录当前最新数据的的时间
    private LocalDate localDate;


    public COVIDData all() {
        return dao.getCovidData();
    }


    //定时任务，更新数据，每隔5小时更新一次数据
    @Scheduled(cron = "0 0 0/5 * * ? ")
    public void update() {
        initCOVIDData();
    }


    /**
     * 初始化数据
     */
    @PostConstruct
    public void initCOVIDData() {
        this.localDate = LocalDate.now();
        int year = this.localDate.getYear();
        int month = this.localDate.getMonthValue();
        int dayOfMonth = this.localDate.getDayOfMonth();
        logger.info("现在请求的是" + year + " " + month + " " + dayOfMonth + "的数据");

        String url = String.format(BASE_URL, month, dayOfMonth, year);
        String body = null;
        try {
            body = this.requestUrl(url);
            List<RegionData> regionDataList = this.parseData(body);//包装数据
            dao.getCovidData().setRegionDatas(regionDataList);
            this.parseConfirmedTotal();//总确诊人数
            this.parseDeathsTotal();//死亡人数
            this.parseRecoveredTotal();//治愈人数
            this.parseActiveTotal();//现存人数
            this.parseTime();//数据来源的时间
            //查询上一次的数据，计算出新增确诊人数
            this.incrementCount();
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("-------------初始化或者更新数据错误--------");
        }
    }

    /**
     * 请求数据
     *
     * @param urlString 请求的地址
     * @return 请求返回的数据
     * @throws Exception
     */
    public String requestUrl(String urlString) throws Exception {
        URI uri = null;
        uri = new URI(urlString);
        ResponseEntity<String> responseEntity = null;
        String body = null;
        RequestEntity<?> requestEntity = null;
        requestEntity = RequestEntity.get(uri).header("User-Agent",
                "Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/76.0")
                .build();
        do {
            try {
                responseEntity = restTemplate.exchange(requestEntity, String.class);
            } catch (Exception e) {
                logger.info("请求不到继续请求");
            }
            if (responseEntity == null) {
                urlString = getLastUrl();
                uri = new URI(urlString);
                requestEntity = RequestEntity.get(uri).header("User-Agent",
                        "Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/76.0")
                        .build();
            }
        } while (responseEntity == null);
        logger.info("请求数据成功");
        body = responseEntity.getBody();
        return body;
    }

    /**
     * 计算当前数据与上一天的总确诊人数的差
     */
    private void incrementCount() {
        LocalDate lastDay = this.localDate.minusDays(1);
        String url = String.format(BASE_URL, lastDay.getMonthValue(),
                lastDay.getDayOfMonth(), lastDay.getYear());
        String body = null;
        List<RegionData> regionDataList = null;
        try {
            body = this.requestUrl(url);
            regionDataList = this.parseData(body);
            Integer integer = this.parseIncrement(regionDataList);
            dao.getCovidData().setIncrementCount(dao.getCovidData().getConfirmedTotalCount() - integer);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 请求上一天的数据，可能今天的数据未更新
     *
     * @return 请求url
     */
    public String getLastUrl() {
        this.localDate = this.localDate.minusDays(1);
        logger.info("现在请求的是" + this.localDate.getYear() + " " + this.localDate.getMonthValue()
                + " " + this.localDate.getDayOfMonth() + "的数据");
        String url = String.format(BASE_URL, this.localDate.getMonthValue(),
                this.localDate.getDayOfMonth(), this.localDate.getYear());
        return url;
    }


    //解析返回的数据，封装成对象
    public List<RegionData> parseData(String body) throws IOException {
        String[] HEADERS = {"FIPS", "Admin2", "Province_State", "Country_Region", "Last_Update", "Lat", "Long_", "Confirmed", "Deaths", "Recovered", "Active", "Combined_Key"};
        Reader reader = new StringReader(body);
        RegionData regionData = null;
        List<RegionData> regionDataList = new ArrayList<>();
        Iterable<CSVRecord> records = CSVFormat.DEFAULT
                .withHeader(HEADERS)
                .withFirstRecordAsHeader()
                .parse(reader);
        for (CSVRecord record : records) {
            regionData = new RegionData();
            regionData.setId(record.get("FIPS"));
            regionData.setCountry(record.get("Country_Region"));
            regionData.setProvince(record.get("Province_State"));
            //"Confirmed", "Deaths", "Recovered", "Active"
            regionData.setConfirmed(Integer.valueOf(record.get("Confirmed")));
            regionData.setDeaths(Integer.valueOf(record.get("Deaths")));
            regionData.setRecovered(Integer.valueOf(record.get("Recovered")));
            regionData.setActive(Integer.valueOf(record.get("Active")));
//            dao.getCovidData().getRegionDatas().add(regionData);
            regionDataList.add(regionData);
        }
        return regionDataList;
    }

    //统计总确诊人数
    public void parseConfirmedTotal() {
        Stream<RegionData> stream = dao.getCovidData().getRegionDatas().stream();
        Integer reduce = stream.map((r) -> r.getConfirmed())
                .reduce(0, (t1, t2) -> t1 + t2);
        dao.getCovidData().setConfirmedTotalCount(reduce);
    }

    //统计总死亡人数
    public void parseDeathsTotal() {
        Stream<RegionData> stream = dao.getCovidData().getRegionDatas().stream();
        Integer reduce = stream.map((r) -> r.getDeaths())
                .reduce(0, (t1, t2) -> t1 + t2);
        dao.getCovidData().setDeathsTotalCount(reduce);
    }


    //统计总治愈人数
    public void parseRecoveredTotal() {
        Stream<RegionData> stream = dao.getCovidData().getRegionDatas().stream();
        Integer reduce = stream.map((r) -> r.getRecovered())
                .reduce(0, (t1, t2) -> t1 + t2);
        dao.getCovidData().setRecoveredTotalCount(reduce);
    }


    //统计总现存人数
    public void parseActiveTotal() {
        Stream<RegionData> stream = dao.getCovidData().getRegionDatas().stream();
        Integer reduce = stream.map((r) -> r.getActive())
                .reduce(0, (t1, t2) -> t1 + t2);
        dao.getCovidData().setActiveTotalCount(reduce);
    }

    //设置来源数据的时间
    public void parseTime() {
        dao.getCovidData().setYear(this.localDate.getYear());
        dao.getCovidData().setMonth(this.localDate.getMonthValue());
        dao.getCovidData().setDay(this.localDate.getDayOfMonth());
    }

    public Integer parseIncrement(List<RegionData> regionDataList) {
        Stream<RegionData> stream = regionDataList.stream();
        Integer reduce = stream.map(RegionData::getConfirmed)
                .reduce(0, (r1, r2) -> r1 + r2);
        return reduce;
    }


    /**
     * 进行模糊查询，国家或者省份都可以
     * @param query  需要查询的字符串
     * @return
     */
    public List<RegionData> search(String query) {
        logger.info("进行模糊查询,查询字符串为"+query);
        List<RegionData> regionDataList = new ArrayList<>();
        Stream<RegionData> stream = dao.getCovidData().getRegionDatas().parallelStream();
        stream.filter((r) -> {
            String country = r.getCountry().toLowerCase();
            String province = r.getProvince().toLowerCase();
            if (country.contains(query.toLowerCase()) || province.contains(query.toLowerCase())) {
                return true;
            }
            return false;
        }).forEach((r) -> {
            regionDataList.add(r);
        });
        return regionDataList;
    }
}
